package dungeons;

import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

import squares.Direction;
import squares.IllegalSquareException;
import squares.RockSquare;
import squares.Square;


/**
 * this plateau is a special kind of dungeon that only consist of squares only in the x and y direction.
 * @invar
 * 		the maximum range of one direction of the dimension is all bigger then zero and smaller then Long.MAX_VALUE
 * 		the maximum range of the other two directions is zero
 * 		| maxX > 0 && maxY == 0 && maxZ == 0	||
 * 		|maxX == 0 && maxY > 0 && maxZ == 0		||
 * 		|maxX == 0 && maxY == 0 && maxZ > 0
 * @version 1.0
 * @author Gilles Groven & Jochen Tombal
 *
 * @param <E>
 * 			this parameter represents the kind of square this dungeon contains.
 */

public class Shaft<E extends Square> extends SingleDungeon<E> {

	/**
	 * an enum for the 3 different direction to which a shaft is limited, X for x/east direction,
	 * 	Y for y/north direction, Z for z/Ceiling direction
	 */
	public enum shaftDirection {X, Y, Z}
	/**
	 * variable registering the direction of the shaft to which it is limited
	 */
	private final shaftDirection myShaftDirection;

	
	/**
	 * Creates a Shaft as an instance of Dungeon limited to a maximum value in a single direction.
	 * 
	 * @param k	
	 * 			The maximum length where squares can be added in its direction
	 * @param d 
	 * 			the direction in which the Shaft will be limited
	 * @post the max x value of this dungeon is the given long k if the shaftDirection is X, otherwisze 0
	 * 		|if (d == shaftDirection.X)
	 *		|	then new.getMaxX()==k
	 *		|else new.getMaxX()==0
	 * @post the max y value of this dungeon is the given long k if the shaftDirection is Y, otherwisze 0
	 * 		|if (d == shaftDirection.Y)
	 *		|	then new.getMaxY()==k
	 *		|else new.getMaxY()==0
	 *@post the max z value of this dungeon is the given long k if the shaftDirection is Z, otherwisze 0
	 * 		|if (d == shaftDirection.Z)
	 *		|	then new.getMaxZ()==k
	 *		|else new.getMaxZ()==0
	 *@post the given shaft direction, shaftDirection d, is equal to the shaft direction, myShaftDirection
	 *		| new.getMyShaftDirection() == myShaftDirection
	 */
	public Shaft(long k, shaftDirection d) throws IllegalArgumentException {
		super(0, 0, 0);
		if (d == shaftDirection.X)
			setMaxX(k);
		if (d == shaftDirection.Y)
			setMaxY(k);
		if (d == shaftDirection.Z)
			setMaxZ(k);
		myShaftDirection = d;
	}
	/**
	 * Creates a Shaft as an instance of Dungeon limited to a maximum value
	 * in the X-direction when there is no direction specified by the user.
     *
	 * @param k 
	 * 			The maximum length where squares can be added in its direction
	 * @post the max x value of this dungeon is the given long k in the X-direction.
	 * 		| new.getmaxX == k
	 * @post the shaft direction, myShaftDirection, is equal to the X-direction
	 *		| new.getMyShaftDirection() == shaftDirection.X
	 */
	public Shaft(long k){
		super(k,0,0);
		myShaftDirection= shaftDirection.X;
	}
	/**
	 * Creates a Shaft as an instance of Dungeon limited to a maximum 10
	 * in a single direction when there is no maximum value specified by the user.
	 * 
	 * @param d
	 * 			the direction in which the Shaft will be limited
	 * @post the max x value of this dungeon is 10 if the shaftDirection is X, otherwisze 0
	 *		|if (d == shaftDirection.X)
	 *		|	then new.getMaxX()==10
	 *		|else new.getMaxX()==0
	 * @post the max y value of this dungeon is 10 if the shaftDirection is Y, otherwisze 0
	 * 		|if (d == shaftDirection.Y)
	 *		|	then new.getMaxY()==10
	 *		|else new.getMaxY()==0
	 *@post the max z value of this dungeon is 10 if the shaftDirection is Z, otherwisze 0
	 * 		|if (d == shaftDirection.Z)
	 *		|	then new.getMaxZ()==10
	 *		|else new.getMaxZ()==0
	 * @post the given shaft direction, shaftDirection d, is equal to the shaft direction, myShaftDirection
	 *		| new.getMyShaftDirection() == myShaftDirection
	 */
	public Shaft (shaftDirection d)
	{
		super(0, 0, 0);
		if (d == shaftDirection.X)
			setMaxX(10);
		if (d == shaftDirection.Y)
			setMaxY(10);
		if (d == shaftDirection.Z)
			setMaxZ(10);
		myShaftDirection = d;
	}
	
	
	
	
	/** 
	 * @return the shaftDirection to which this shaft is limited
	 */
	public shaftDirection getMyShaftDirection() {
		return myShaftDirection;
	}
	/**
	 * 
	 * @throws IllegalArgumentException
	 * 		if the coordinates are not valid (one or more below zero), out of range of the dimensions, all the same.
	 * 		also if adding this square will give the dungeon more the 20% squares that are slippery.	
	 * 		also if adding the square on the given position will result in borders that are not allowed by the program.
	 * 		also if a rock square is added
	 * 		| if((x== y && y==z) ||x > maxX || y> maxY || z>maxZ || x<0 || y<0 || z<0)
	 * 		| if(square.getSlipperyFloor() && toManySlippery())
	 * 		| if(duplicateCoordinate(x,y,z))
	 * 		| if(!willHaveValidBorders(square,x,y,z))
	 * 		| if(square instanceof RockSquare)
	 *@post
	 * 		the given square is added on the given position if it has no rootdungeon
	 * 		|if (getRootDungeon() != null)
	 * 		|	then getSquareAt(x,y,z) == square 
	 */
	@Override
	public void addSquare(E square, long x, long y, long z)
			throws IllegalSquareException, IllegalArgumentException {
		if (square == null)
			throw new IllegalSquareException("invalid square");
		else if (x > getMaxX() || y > getMaxY() || z > getMaxZ())
			throw new IllegalArgumentException(
					"The coordinate is out of range for this shaft.");
		else if (x < 0 || y < 0 || z < 0)
			throw new IllegalArgumentException(
					"one or more coordinates are smaller then zero.");
		else if (getSquares().contains(square))
			throw new IllegalSquareException(
					"this square is allready present in the shaft");
		else if (square.getSlipperyFloor()) {
			if (toManySlippery())
				throw new IllegalArgumentException(
						"there are already to many slippery squares.");
		} else if (duplicateCoordinate(x, y, z))
			throw new IllegalSquareException(
					"already a square on this location.");
		else if (!willHaveValidBorders(square, x, y, z))
			throw new IllegalArgumentException(
					"placing here will result in bad borders.");
		else if (square instanceof RockSquare)
			throw new IllegalArgumentException("a shaft can't contain rocks");
		 {
			if (square.isConnectedToOtherSquare())
				throw new IllegalSquareException(
						"the square is already connected to a other square");
		 }
		if (getRootDungeon() != null)
		{
			long[] coords = new long[3];
			coords = this.getRootDungeon().getRootLocationOfDungeon(this);
			x =+ coords[0];
			y =+ coords[1];
			z =+ coords[2];
			this.getRootDungeon().addSquareToSpecifiedDungeon(this, square, x, y, z);
		}
		else if(square instanceof RockSquare)
		{
			addSquareToHashMap(square, x, y, z);
			mergeBorders(square, x, y, z);
			((RockSquare)square).adjustTemperature();
		}
		else
		{
		addSquareToHashMap(square, x, y, z);
		mergeBorders(square, x, y, z);
		((Square) square).adjustTemperatureSpace();
		((Square) square).adjustHumiditySpace();
		getSquares().add(square);
		}

	}
	
	/**
	 * This methode checks if adding this square to the given coordinates will cause valid borders
	 * 
	 * @return	true if there are only open borders allowed between two squares
	 * 			|if added
	 * 			|	for(Direction direction: Direction.values())
	 *			|		if(!((Square) square).getBorderAt(direction)hasOnlyOneSquareAttached())
	 *			|			then ((Square) square).getBorderAt(direction) instanceof Open
	 *
	 */
	@Override
	protected boolean willHaveValidBorders(E square, long x, long y, long z)
	{
		if (myShaftDirection == shaftDirection.Z) {
			if (getSquareAt(x, y, z + 1) == null
					|| (((Square) getSquareAt(x, y, z + 1))
							.getBorderAt(Direction.FLOOR) instanceof borders.Open && ((Square) square)
							.getBorderAt(Direction.CEILING) instanceof borders.Open))
				if (getSquareAt(x, y, z - 1) == null
						|| (((Square) getSquareAt(x, y, z - 1))
								.getBorderAt(Direction.CEILING) instanceof borders.Open && ((Square) square)
								.getBorderAt(Direction.FLOOR) instanceof borders.Open))
					return true;
		} else if (myShaftDirection == shaftDirection.Y) {
			if (getSquareAt(x, y + 1, z) == null
					|| (((Square) getSquareAt(x, y + 1, z))
							.getBorderAt(Direction.NORTH) instanceof borders.Open || ((Square) square)
							.getBorderAt(Direction.SOUTH) instanceof borders.Open))
				if (getSquareAt(x, y - 1, z) == null
						|| (((Square) getSquareAt(x, y - 1, z))
								.getBorderAt(Direction.SOUTH) instanceof borders.Open || ((Square) square)
								.getBorderAt(Direction.NORTH) instanceof borders.Open))
					return true;
		} else if (myShaftDirection == shaftDirection.X) {
			if (getSquareAt(x + 1, y, z) == null
					|| (((Square) getSquareAt(x + 1, y, z))
							.getBorderAt(Direction.EAST) instanceof borders.Open || ((Square) square)
							.getBorderAt(Direction.WEST) instanceof borders.Open))
				if (getSquareAt(x - 1, y, z) == null
						|| (((Square) getSquareAt(x - 1, y, z))
								.getBorderAt(Direction.WEST) instanceof borders.Open || ((Square) square)
								.getBorderAt(Direction.EAST) instanceof borders.Open))
					return true;
		}
		return false;

	}

	/**
	 * An iterator for returning all the squares of a dungeon one at a time.
	 * @return	returns the iterator of this dungeon 
	 */

	@Override
	public Iterator<E> iterator() {
	      return new Iterator<E>() {
	            private Iterator<Long> x;

	    	  	/**
	    	  	 * @return 	true if there is still a next square in the enumeration
	    	  	 */
	            public boolean hasNext() {
	            	if(x == null)
	            	{
	            		if(myShaftDirection == shaftDirection.X)
	            		{
	            			x = getDimensions().keySet().iterator();
	            		}
	            		else if(myShaftDirection == shaftDirection.Y)
	            		{
	            			x = getDimensions().get(0).keySet().iterator();
	            		}
	            		else if(myShaftDirection == shaftDirection.Z)
	            		{
	            			x = getDimensions().get(0).get(0).keySet().iterator();
	            		}
	            		else
	            			return false;
	            	}
	            	
	            		return x.hasNext();
	            	
	            }
	            	
	           
	            /**
	             * @return the next square in the iterator
	             * @throws 	NoSuchElementException	
	             * 			if there are no more squares in the enumeration.
	             * 			|!hasNext() 
	             */
	            public E next() throws NoSuchElementException {
	                if (!hasNext())
	                    throw new NoSuchElementException();
	                if(myShaftDirection == shaftDirection.X)
            		{
            			return getSquareAt(x.next(),0,0);
            		}
            		else if(myShaftDirection == shaftDirection.Y)
            		{
            			return getSquareAt(0,x.next(),0);
            		}
            		else 
            		{
            			return getSquareAt(0,0,x.next());
            		}
	               
	                	
	            }
	             /**
	             *@throws UnsupportedOperationException
	             *		indicates that the requested operation, remove(), is not supported
	             */
	            public void remove() throws UnsupportedOperationException {
	                throw new UnsupportedOperationException();
	            }
	        };
		}

	/**
	 *@return a Shaft may not contain any rocks so there are no rocks of a 
	 *			temperature higher or equal to 200 degrees.
	 * 		|result == null
	 */
	@Override
	public HashSet<RockSquare> getRocksOf200Degrees() {
		return null;
	}
}
